package net.osmand.plus.download.ui;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.PopupMenu;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.TypedValue;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.ExpandableListContextMenuInfo;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import net.osmand.AndroidUtils;
import net.osmand.IndexConstants;
import net.osmand.map.ITileSource;
import net.osmand.plus.ContextMenuAdapter;
import net.osmand.plus.ContextMenuAdapter.ItemClickListener;
import net.osmand.plus.ContextMenuItem;
import net.osmand.plus.IconsCache;
import net.osmand.plus.OsmandApplication;
import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.activities.LocalIndexHelper;
import net.osmand.plus.activities.LocalIndexHelper.LocalIndexType;
import net.osmand.plus.activities.LocalIndexInfo;
import net.osmand.plus.activities.OsmandBaseExpandableListAdapter;
import net.osmand.plus.base.OsmandExpandableListFragment;
import net.osmand.plus.dialogs.DirectionsDialogs;
import net.osmand.plus.download.DownloadActivity;
import net.osmand.plus.download.DownloadIndexesThread.DownloadEvents;
import net.osmand.plus.download.IndexItem;
import net.osmand.plus.helpers.FileNameTranslationHelper;
import net.osmand.util.Algorithms;
import java.io.File;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
public class LocalIndexesFragment extends OsmandExpandableListFragment implements DownloadEvents {
public static final Pattern ILLEGAL_FILE_NAME_CHARACTERS = Pattern.compile("[?:\"*|/\\<>]");
private LoadLocalIndexTask asyncLoader;
private Map<String, IndexItem> filesToUpdate = new HashMap<>();
private LocalIndexesAdapter listAdapter;
private AsyncTask<LocalIndexInfo, ?, ?> operationTask;
private boolean selectionMode = false;
private Set<LocalIndexInfo> selectedItems = new LinkedHashSet<>();
private ContextMenuAdapter optionsMenuAdapter;
private ActionMode actionMode;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.local_index, container, false);
getDownloadActivity().setSupportProgressBarIndeterminateVisibility(false);
getDownloadActivity().getAccessibilityAssistant().registerPage(view, DownloadActivity.LOCAL_TAB_NUMBER);
ExpandableListView listView = (ExpandableListView) view.findViewById(android.R.id.list);
listAdapter = new LocalIndexesAdapter(getDownloadActivity());
listView.setAdapter(listAdapter);
expandAllGroups();
setListView(listView);
return view;
}
@SuppressWarnings({"unchecked", "deprecation"})
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (asyncLoader == null || asyncLoader.getResult() == null) {
// getLastNonConfigurationInstance method should be in onCreate() method
// (onResume() doesn't work)
Object indexes = getActivity().getLastNonConfigurationInstance();
if (indexes instanceof List<?>) {
asyncLoader = new LoadLocalIndexTask();
asyncLoader.setResult((List<LocalIndexInfo>) indexes);
}
}
setHasOptionsMenu(true);
}
@Override
public void onResume() {
super.onResume();
if (asyncLoader == null || asyncLoader.getResult() == null) {
reloadData();
}
getExpandableListView().setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
long packedPos = ((ExpandableListContextMenuInfo) menuInfo).packedPosition;
int group = ExpandableListView.getPackedPositionGroup(packedPos);
int child = ExpandableListView.getPackedPositionChild(packedPos);
if (child >= 0 && group >= 0) {
final LocalIndexInfo point = listAdapter.getChild(group, child);
showContextMenu(point);
}
}
});
}
public void reloadData() {
List<IndexItem> itemsToUpdate = getDownloadActivity().getDownloadThread().getIndexes().getItemsToUpdate();
filesToUpdate.clear();
for (IndexItem ii : itemsToUpdate) {
filesToUpdate.put(ii.getTargetFileName(), ii);
}
LoadLocalIndexTask current = asyncLoader;
if (current == null || current.getStatus() == AsyncTask.Status.FINISHED ||
current.isCancelled() || current.getResult() != null) {
asyncLoader = new LoadLocalIndexTask();
asyncLoader.execute();
}
}
private void showContextMenu(final LocalIndexInfo info) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
final ContextMenuAdapter adapter = new ContextMenuAdapter();
basicFileOperation(info, adapter);
OsmandPlugin.onContextMenuActivity(getActivity(), null, info, adapter);
String[] values = adapter.getItemNames();
builder.setItems(values, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ContextMenuItem item = adapter.getItem(which);
if (item.getItemClickListener() != null) {
item.getItemClickListener().onContextMenuClick(null,
item.getTitleId(), which, false);
}
}
});
builder.show();
}
private void basicFileOperation(final LocalIndexInfo info, ContextMenuAdapter adapter) {
ItemClickListener listener = new ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter, int resId, int pos, boolean isChecked) {
return performBasicOperation(resId, info);
}
};
if (info.getType() == LocalIndexType.MAP_DATA || info.getType() == LocalIndexType.SRTM_DATA ||
info.getType() == LocalIndexType.WIKI_DATA) {
if (!info.isBackupedData()) {
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_backup, getContext())
.setListener(listener)
.setPosition(1).createItem());
}
}
if (info.isBackupedData()) {
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_restore, getContext())
.setListener(listener)
.setPosition(2).createItem());
}
if (info.getType() != LocalIndexType.TTS_VOICE_DATA && info.getType() != LocalIndexType.VOICE_DATA
&& info.getType() != LocalIndexType.FONT_DATA) {
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.shared_string_rename, getContext())
.setListener(listener)
.setPosition(3).createItem());
}
adapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.shared_string_delete, getContext())
.setListener(listener)
.setPosition(4).createItem());
}
private boolean performBasicOperation(int resId, final LocalIndexInfo info) {
if (resId == R.string.shared_string_rename) {
renameFile(getActivity(), new File(info.getPathToData()), new RenameCallback() {
@Override
public void renamedTo(File file) {
getDownloadActivity().reloadLocalIndexes();
}
});
} else if (resId == R.string.clear_tile_data) {
AlertDialog.Builder confirm = new AlertDialog.Builder(getActivity());
confirm.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.CLEAR_TILES_OPERATION).execute(info);
}
});
confirm.setNegativeButton(R.string.shared_string_no, null);
String fn = FileNameTranslationHelper.getFileName(getActivity(),
getMyApplication().getResourceManager().getOsmandRegions(),
info.getFileName());
confirm.setMessage(getString(R.string.delete_confirmation_msg, fn));
confirm.show();
} else if (resId == R.string.local_index_mi_restore) {
new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.RESTORE_OPERATION).execute(info);
} else if (resId == R.string.shared_string_delete) {
AlertDialog.Builder confirm = new AlertDialog.Builder(getActivity());
confirm.setPositiveButton(R.string.shared_string_yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.DELETE_OPERATION).execute(info);
}
});
confirm.setNegativeButton(R.string.shared_string_no, null);
String fn = FileNameTranslationHelper.getFileName(getActivity(),
getMyApplication().getResourceManager().getOsmandRegions(),
info.getFileName());
confirm.setMessage(getString(R.string.delete_confirmation_msg, fn));
confirm.show();
} else if (resId == R.string.local_index_mi_backup) {
new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.BACKUP_OPERATION).execute(info);
}
return true;
}
public static void renameFile(final Activity a, final File f, final RenameCallback callback) {
AlertDialog.Builder b = new AlertDialog.Builder(a);
if (f.exists()) {
int xt = f.getName().lastIndexOf('.');
final String ext = xt == -1 ? "" : f.getName().substring(xt);
final String originalName = xt == -1 ? f.getName() : f.getName().substring(0, xt);
final EditText editText = new EditText(a);
editText.setText(originalName);
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
Editable text = editText.getText();
if (text.length() >= 1) {
if (ILLEGAL_FILE_NAME_CHARACTERS.matcher(text).find()) {
editText.setError(a.getString(R.string.file_name_containes_illegal_char));
}
}
}
});
b.setTitle(R.string.shared_string_rename);
int leftPadding = AndroidUtils.dpToPx(a, 24f);
int topPadding = AndroidUtils.dpToPx(a, 4f);
b.setView(editText, leftPadding, topPadding, leftPadding, topPadding);
// Behaviour will be overwritten later;
b.setPositiveButton(R.string.shared_string_save, null);
b.setNegativeButton(R.string.shared_string_cancel, null);
final AlertDialog alertDialog = b.create();
alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(DialogInterface dialog) {
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
String newName = editText.getText().toString() + ext;
if (ILLEGAL_FILE_NAME_CHARACTERS.matcher(newName).find()) {
Toast.makeText(a, R.string.file_name_containes_illegal_char,
Toast.LENGTH_LONG).show();
return;
}
File dest = new File(f.getParentFile(), newName);
if (dest.exists()) {
Toast.makeText(a, R.string.file_with_name_already_exists,
Toast.LENGTH_LONG).show();
return;
} else {
if (!dest.getParentFile().exists()) {
dest.getParentFile().mkdirs();
}
if (f.renameTo(dest)) {
if (callback != null) {
callback.renamedTo(dest);
}
} else {
Toast.makeText(a, R.string.file_can_not_be_renamed,
Toast.LENGTH_LONG).show();
}
}
alertDialog.dismiss();
}
});
}
});
alertDialog.show();
}
}
public class LoadLocalIndexTask extends AsyncTask<Void, LocalIndexInfo, List<LocalIndexInfo>>
implements AbstractLoadLocalIndexTask {
private List<LocalIndexInfo> result;
@Override
protected List<LocalIndexInfo> doInBackground(Void... params) {
LocalIndexHelper helper = new LocalIndexHelper(getMyApplication());
return helper.getLocalIndexData(this);
}
@Override
public void loadFile(LocalIndexInfo... loaded) {
if (!isCancelled()) {
publishProgress(loaded);
}
}
@Override
protected void onPreExecute() {
getDownloadActivity().setSupportProgressBarIndeterminateVisibility(true);
listAdapter.clear();
}
@Override
protected void onProgressUpdate(LocalIndexInfo... values) {
for (LocalIndexInfo v : values) {
listAdapter.addLocalIndexInfo(v);
}
listAdapter.notifyDataSetChanged();
expandAllGroups();
}
public void setResult(List<LocalIndexInfo> result) {
this.result = result;
listAdapter.clear();
if (result != null) {
for (LocalIndexInfo v : result) {
listAdapter.addLocalIndexInfo(v);
}
listAdapter.notifyDataSetChanged();
expandAllGroups();
onPostExecute(result);
}
}
@Override
protected void onPostExecute(List<LocalIndexInfo> result) {
this.result = result;
listAdapter.sortData();
if (getDownloadActivity() != null) {
getDownloadActivity().setSupportProgressBarIndeterminateVisibility(false);
getDownloadActivity().setLocalIndexInfos(result);
}
}
public List<LocalIndexInfo> getResult() {
return result;
}
}
public static class LocalIndexOperationTask extends AsyncTask<LocalIndexInfo, LocalIndexInfo, String> {
protected static int DELETE_OPERATION = 1;
protected static int BACKUP_OPERATION = 2;
protected static int RESTORE_OPERATION = 3;
protected static int CLEAR_TILES_OPERATION = 4;
private final int operation;
private DownloadActivity a;
private LocalIndexesAdapter listAdapter;
public LocalIndexOperationTask(DownloadActivity a, LocalIndexesAdapter listAdapter, int operation) {
this.a = a;
this.listAdapter = listAdapter;
this.operation = operation;
}
private boolean move(File from, File to) {
if (!to.getParentFile().exists()) {
to.getParentFile().mkdirs();
}
return from.renameTo(to);
}
private File getFileToBackup(LocalIndexInfo i) {
if (!i.isBackupedData()) {
return new File(getMyApplication().getAppPath(IndexConstants.BACKUP_INDEX_DIR), i.getFileName());
}
return new File(i.getPathToData());
}
private OsmandApplication getMyApplication() {
return (OsmandApplication) a.getApplication();
}
private File getFileToRestore(LocalIndexInfo i) {
if (i.isBackupedData()) {
File parent = new File(i.getPathToData()).getParentFile();
if (i.getOriginalType() == LocalIndexType.MAP_DATA) {
if (i.getFileName().endsWith(IndexConstants.BINARY_ROAD_MAP_INDEX_EXT)) {
parent = getMyApplication().getAppPath(IndexConstants.ROADS_INDEX_DIR);
} else {
parent = getMyApplication().getAppPath(IndexConstants.MAPS_PATH);
}
} else if (i.getOriginalType() == LocalIndexType.TILES_DATA) {
parent = getMyApplication().getAppPath(IndexConstants.TILES_INDEX_DIR);
} else if (i.getOriginalType() == LocalIndexType.SRTM_DATA) {
parent = getMyApplication().getAppPath(IndexConstants.SRTM_INDEX_DIR);
} else if (i.getOriginalType() == LocalIndexType.WIKI_DATA) {
parent = getMyApplication().getAppPath(IndexConstants.WIKI_INDEX_DIR);
} else if (i.getOriginalType() == LocalIndexType.TTS_VOICE_DATA) {
parent = getMyApplication().getAppPath(IndexConstants.VOICE_INDEX_DIR);
} else if (i.getOriginalType() == LocalIndexType.VOICE_DATA) {
parent = getMyApplication().getAppPath(IndexConstants.VOICE_INDEX_DIR);
} else if (i.getOriginalType() == LocalIndexType.FONT_DATA) {
parent = getMyApplication().getAppPath(IndexConstants.FONT_INDEX_DIR);
}
return new File(parent, i.getFileName());
}
return new File(i.getPathToData());
}
@Override
protected String doInBackground(LocalIndexInfo... params) {
int count = 0;
int total = 0;
for (LocalIndexInfo info : params) {
if (!isCancelled()) {
boolean successfull = false;
if (operation == DELETE_OPERATION) {
File f = new File(info.getPathToData());
successfull = Algorithms.removeAllFiles(f);
if (successfull) {
getMyApplication().getResourceManager().closeFile(info.getFileName());
}
} else if (operation == RESTORE_OPERATION) {
successfull = move(new File(info.getPathToData()), getFileToRestore(info));
if (successfull) {
info.setBackupedData(false);
}
} else if (operation == BACKUP_OPERATION) {
successfull = move(new File(info.getPathToData()), getFileToBackup(info));
if (successfull) {
info.setBackupedData(true);
getMyApplication().getResourceManager().closeFile(info.getFileName());
}
} else if (operation == CLEAR_TILES_OPERATION) {
ITileSource src = (ITileSource) info.getAttachedObject();
if(src != null) {
src.clearTiles(info.getPathToData());
}
}
total++;
if (successfull) {
count++;
publishProgress(info);
}
}
}
if (operation == DELETE_OPERATION) {
a.getDownloadThread().updateLoadedFiles();
}
if (operation == DELETE_OPERATION) {
return a.getString(R.string.local_index_items_deleted, count, total);
} else if (operation == BACKUP_OPERATION) {
return a.getString(R.string.local_index_items_backuped, count, total);
} else if (operation == RESTORE_OPERATION) {
return a.getString(R.string.local_index_items_restored, count, total);
}
return "";
}
@Override
protected void onProgressUpdate(LocalIndexInfo... values) {
if (listAdapter != null) {
if (operation == DELETE_OPERATION) {
listAdapter.delete(values);
} else if (operation == BACKUP_OPERATION) {
listAdapter.move(values, false);
} else if (operation == RESTORE_OPERATION) {
listAdapter.move(values, true);
}
}
}
@Override
protected void onPreExecute() {
a.setProgressBarIndeterminateVisibility(true);
}
@Override
protected void onPostExecute(String result) {
a.setProgressBarIndeterminateVisibility(false);
if(result != null && result.length() > 0) {
Toast.makeText(a, result, Toast.LENGTH_LONG).show();
}
if (operation == RESTORE_OPERATION || operation == BACKUP_OPERATION || operation == CLEAR_TILES_OPERATION) {
a.reloadLocalIndexes();
} else {
a.newDownloadIndexes();
}
}
}
@Override
public void newDownloadIndexes() {
reloadData();
}
@Override
public void downloadHasFinished() {
reloadData();
}
@Override
public void downloadInProgress() {
}
@Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
LocalIndexInfo child = listAdapter.getChild(groupPosition, childPosition);
if (!selectionMode) {
openPopUpMenu(v, child);
return true;
}
selectedItems.add(child);
listAdapter.notifyDataSetChanged();
return true;
}
public Set<LocalIndexInfo> getSelectedItems() {
return selectedItems;
}
@Override
public void onPause() {
super.onPause();
if (operationTask != null) {
operationTask.cancel(true);
}
}
@Override
public void onDestroy() {
super.onDestroy();
asyncLoader.cancel(true);
}
@SuppressWarnings("deprecation")
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
if (!this.isAdded()) {
return;
}
//fixes issue when local files not shown after switching tabs
//Next line throws NPE in some circumstances when called from dashboard and listAdpater=null is not checked for. (Checking !this.isAdded above is not sufficient!)
if (listAdapter != null && listAdapter.getGroupCount() == 0 && getDownloadActivity().getLocalIndexInfos().size() > 0) {
for (LocalIndexInfo info : getDownloadActivity().getLocalIndexInfos()) {
listAdapter.addLocalIndexInfo(info);
}
listAdapter.sortData();
getExpandableListView().setAdapter(listAdapter);
expandAllGroups();
}
ActionBar actionBar = getDownloadActivity().getSupportActionBar();
//hide action bar from downloadindexfragment
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
optionsMenuAdapter = new ContextMenuAdapter();
ItemClickListener listener = new ContextMenuAdapter.ItemClickListener() {
@Override
public boolean onContextMenuClick(ArrayAdapter<ContextMenuItem> adapter,
int itemId, int pos, boolean isChecked) {
localOptionsMenu(itemId);
return true;
}
};
optionsMenuAdapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_reload, getContext())
.setIcon(R.drawable.ic_action_refresh_dark)
.setListener(listener)
.createItem());
optionsMenuAdapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.shared_string_delete, getContext())
.setIcon(R.drawable.ic_action_delete_dark)
.setListener(listener)
.createItem());
optionsMenuAdapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_backup, getContext())
.setListener(listener)
.createItem());
optionsMenuAdapter.addItem(new ContextMenuItem.ItemBuilder()
.setTitleId(R.string.local_index_mi_restore, getContext())
.setListener(listener)
.createItem());
// doesn't work correctly
//int max = getResources().getInteger(R.integer.abs__max_action_buttons);
int max = 3;
SubMenu split = null;
for (int j = 0; j < optionsMenuAdapter.length(); j++) {
MenuItem item;
ContextMenuItem contextMenuItem = optionsMenuAdapter.getItem(j);
if (j + 1 >= max && optionsMenuAdapter.length() > max) {
if (split == null) {
split = menu.addSubMenu(0, 1, j + 1, R.string.shared_string_more_actions);
split.setIcon(R.drawable.ic_overflow_menu_white);
split.getItem();
MenuItemCompat.setShowAsAction(split.getItem(), MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
}
item = split.add(0, contextMenuItem.getTitleId(), j + 1, contextMenuItem.getTitle());
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
} else {
item = menu.add(0, contextMenuItem.getTitleId(), j + 1, contextMenuItem.getTitle());
MenuItemCompat.setShowAsAction(item, MenuItemCompat.SHOW_AS_ACTION_ALWAYS);
}
if (contextMenuItem.getIcon() != -1) {
item.setIcon(contextMenuItem.getIcon());
}
}
if (operationTask == null || operationTask.getStatus() == AsyncTask.Status.FINISHED) {
menu.setGroupVisible(0, true);
} else {
menu.setGroupVisible(0, false);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId();
for (int i = 0; i < optionsMenuAdapter.length(); i++) {
ContextMenuItem contextMenuItem = optionsMenuAdapter.getItem(i);
if (itemId == contextMenuItem.getTitleId()) {
contextMenuItem.getItemClickListener().onContextMenuClick(null, itemId, i, false);
return true;
}
}
return super.onOptionsItemSelected(item);
}
public void doAction(int actionResId) {
if (actionResId == R.string.local_index_mi_backup) {
operationTask = new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.BACKUP_OPERATION);
} else if (actionResId == R.string.shared_string_delete) {
operationTask = new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.DELETE_OPERATION);
} else if (actionResId == R.string.local_index_mi_restore) {
operationTask = new LocalIndexOperationTask(getDownloadActivity(), listAdapter, LocalIndexOperationTask.RESTORE_OPERATION);
} else {
operationTask = null;
}
if (operationTask != null) {
operationTask.execute(selectedItems.toArray(new LocalIndexInfo[selectedItems.size()]));
}
if (actionMode != null) {
actionMode.finish();
}
}
private void expandAllGroups() {
for (int i = 0; i < listAdapter.getGroupCount(); i++) {
getExpandableListView().expandGroup(i);
}
}
private void openSelectionMode(final int actionResId, final int actionIconId,
final DialogInterface.OnClickListener listener) {
String value = getString(actionResId);
if (value.endsWith("...")) {
value = value.substring(0, value.length() - 3);
}
final String actionButton = value;
if (listAdapter.getGroupCount() == 0) {
listAdapter.cancelFilter();
expandAllGroups();
listAdapter.notifyDataSetChanged();
Toast.makeText(getDownloadActivity(), getString(R.string.local_index_no_items_to_do, actionButton.toLowerCase()), Toast.LENGTH_SHORT).show();
return;
}
expandAllGroups();
selectionMode = true;
selectedItems.clear();
actionMode = getDownloadActivity().startSupportActionMode(new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
selectionMode = true;
MenuItem it = menu.add(actionResId);
if (actionIconId != 0) {
it.setIcon(actionIconId);
}
MenuItemCompat.setShowAsAction(it, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM |
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (selectedItems.isEmpty()) {
Toast.makeText(getDownloadActivity(),
getString(R.string.local_index_no_items_to_do, actionButton.toLowerCase()), Toast.LENGTH_SHORT).show();
return true;
}
AlertDialog.Builder builder = new AlertDialog.Builder(getDownloadActivity());
builder.setMessage(getString(R.string.local_index_action_do, actionButton.toLowerCase(), selectedItems.size()));
builder.setPositiveButton(actionButton, listener);
builder.setNegativeButton(R.string.shared_string_cancel, null);
builder.show();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
selectionMode = false;
listAdapter.cancelFilter();
expandAllGroups();
listAdapter.notifyDataSetChanged();
}
});
//findViewById(R.id.DescriptionText).setVisibility(View.GONE);
listAdapter.notifyDataSetChanged();
}
public void localOptionsMenu(final int itemId) {
if (itemId == R.string.local_index_mi_reload) {
getDownloadActivity().reloadLocalIndexes();
} else if (itemId == R.string.shared_string_delete) {
openSelectionMode(itemId, R.drawable.ic_action_delete_dark,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doAction(itemId);
}
}, null);
} else if (itemId == R.string.local_index_mi_backup) {
openSelectionMode(itemId, R.drawable.ic_type_archive,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doAction(itemId);
}
}, EnumSet.of(LocalIndexType.MAP_DATA, LocalIndexType.WIKI_DATA, LocalIndexType.SRTM_DATA));
} else if (itemId == R.string.local_index_mi_restore) {
openSelectionMode(itemId, R.drawable.ic_type_archive,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
doAction(itemId);
}
}, EnumSet.of(LocalIndexType.DEACTIVATED));
}
}
public void openSelectionMode(int stringRes, int darkIcon, DialogInterface.OnClickListener listener,
EnumSet<LocalIndexType> filter) {
if (filter != null) {
listAdapter.filterCategories(filter);
}
openSelectionMode(stringRes, darkIcon, listener);
}
protected class LocalIndexesAdapter extends OsmandBaseExpandableListAdapter {
Map<LocalIndexInfo, List<LocalIndexInfo>> data = new LinkedHashMap<>();
List<LocalIndexInfo> category = new ArrayList<>();
List<LocalIndexInfo> filterCategory = null;
int warningColor;
int okColor;
int corruptedColor;
DownloadActivity ctx;
public LocalIndexesAdapter(DownloadActivity ctx) {
this.ctx = ctx;
warningColor = ContextCompat.getColor(ctx, R.color.color_warning);
boolean light = ctx.getMyApplication().getSettings().isLightContent();
okColor = ContextCompat.getColor(ctx, light ? R.color.primary_text_light : R.color.primary_text_dark);
corruptedColor = ContextCompat.getColor(ctx, R.color.color_invalid);
}
public void clear() {
data.clear();
category.clear();
filterCategory = null;
notifyDataSetChanged();
}
public void sortData() {
final Collator cl = Collator.getInstance();
for (List<LocalIndexInfo> i : data.values()) {
Collections.sort(i, new Comparator<LocalIndexInfo>() {
@Override
public int compare(LocalIndexInfo lhs, LocalIndexInfo rhs) {
return cl.compare(getNameToDisplay(lhs), getNameToDisplay(rhs));
}
});
}
}
public LocalIndexInfo findCategory(LocalIndexInfo val, boolean backuped) {
for (LocalIndexInfo i : category) {
if (i.isBackupedData() == backuped && val.getType() == i.getType() &&
Algorithms.objectEquals(i.getSubfolder(), val.getSubfolder())) {
return i;
}
}
LocalIndexInfo newCat = new LocalIndexInfo(val.getType(), backuped, val.getSubfolder());
category.add(newCat);
data.put(newCat, new ArrayList<LocalIndexInfo>());
return newCat;
}
public void delete(LocalIndexInfo[] values) {
for (LocalIndexInfo i : values) {
LocalIndexInfo c = findCategory(i, i.isBackupedData());
if (c != null) {
data.get(c).remove(i);
if (data.get(c).size() == 0) {
data.remove(c);
category.remove(c);
}
}
}
notifyDataSetChanged();
}
public void move(LocalIndexInfo[] values, boolean oldBackupState) {
for (LocalIndexInfo i : values) {
LocalIndexInfo c = findCategory(i, oldBackupState);
if (c != null) {
data.get(c).remove(i);
}
c = findCategory(i, !oldBackupState);
if (c != null) {
data.get(c).add(i);
}
}
notifyDataSetChanged();
expandAllGroups();
}
public void cancelFilter() {
filterCategory = null;
notifyDataSetChanged();
}
public void filterCategories(EnumSet<LocalIndexType> types) {
List<LocalIndexInfo> filter = new ArrayList<>();
List<LocalIndexInfo> source = filterCategory == null ? category : filterCategory;
for (LocalIndexInfo info : source) {
if (types.contains(info.getType())) {
filter.add(info);
}
}
filterCategory = filter;
notifyDataSetChanged();
}
public void filterCategories(boolean backup) {
List<LocalIndexInfo> filter = new ArrayList<>();
List<LocalIndexInfo> source = filterCategory == null ? category : filterCategory;
for (LocalIndexInfo info : source) {
if (info.isBackupedData() == backup) {
filter.add(info);
}
}
filterCategory = filter;
notifyDataSetChanged();
}
public void addLocalIndexInfo(LocalIndexInfo info) {
int found = -1;
// search from end
for (int i = category.size() - 1; i >= 0; i--) {
LocalIndexInfo cat = category.get(i);
if (cat.getType() == info.getType() && info.isBackupedData() == cat.isBackupedData() &&
Algorithms.objectEquals(info.getSubfolder(), cat.getSubfolder())) {
found = i;
break;
}
}
if (found == -1) {
found = category.size();
category.add(new LocalIndexInfo(info.getType(), info.isBackupedData(), info.getSubfolder()));
}
if (!data.containsKey(category.get(found))) {
data.put(category.get(found), new ArrayList<LocalIndexInfo>());
}
data.get(category.get(found)).add(info);
}
@Override
public LocalIndexInfo getChild(int groupPosition, int childPosition) {
LocalIndexInfo cat = filterCategory != null ? filterCategory.get(groupPosition) : category.get(groupPosition);
return data.get(cat).get(childPosition);
}
@Override
public long getChildId(int groupPosition, int childPosition) {
// it would be unusable to have 10000 local indexes
return groupPosition * 10000 + childPosition;
}
@Override
public View getChildView(final int groupPosition, final int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
LocalIndexInfoViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(ctx);
convertView = inflater.inflate(R.layout.local_index_list_item, parent, false);
viewHolder = new LocalIndexInfoViewHolder(convertView);
convertView.setTag(viewHolder);
} else {
viewHolder = (LocalIndexInfoViewHolder) convertView.getTag();
}
viewHolder.bindLocalIndexInfo(getChild(groupPosition, childPosition));
return convertView;
}
private String getNameToDisplay(LocalIndexInfo child) {
return FileNameTranslationHelper.getFileName(ctx,
ctx.getMyApplication().getResourceManager().getOsmandRegions(),
child.getFileName());
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
View v = convertView;
LocalIndexInfo group = getGroup(groupPosition);
if (v == null) {
LayoutInflater inflater = LayoutInflater.from(ctx);
v = inflater.inflate(R.layout.download_item_list_section, parent, false);
}
StringBuilder name = new StringBuilder(group.getType().getHumanString(ctx));
if (group.getSubfolder() != null) {
name.append(" ").append(group.getSubfolder());
}
TextView nameView = ((TextView) v.findViewById(R.id.title));
TextView sizeView = ((TextView) v.findViewById(R.id.section_description));
List<LocalIndexInfo> list = data.get(group);
int size = 0;
for (LocalIndexInfo aList : list) {
int sz = aList.getSize();
if (sz < 0) {
size = 0;
break;
} else {
size += sz;
}
}
String sz = "";
if (size > 0) {
if (size > 1 << 20) {
sz = DownloadActivity.formatGb.format(new Object[]{(float) size / (1 << 20)});
} else {
sz = DownloadActivity.formatMb.format(new Object[]{(float) size / (1 << 10)});
}
}
sizeView.setText(sz);
sizeView.setVisibility(View.VISIBLE);
nameView.setText(name.toString());
v.setOnClickListener(null);
TypedValue typedValue = new TypedValue();
Resources.Theme theme = ctx.getTheme();
theme.resolveAttribute(R.attr.ctx_menu_info_view_bg, typedValue, true);
v.setBackgroundColor(typedValue.data);
return v;
}
@Override
public int getChildrenCount(int groupPosition) {
LocalIndexInfo cat = filterCategory != null ? filterCategory.get(groupPosition) : category.get(groupPosition);
return data.get(cat).size();
}
@Override
public LocalIndexInfo getGroup(int groupPosition) {
return filterCategory == null ? category.get(groupPosition) : filterCategory.get(groupPosition);
}
@Override
public int getGroupCount() {
return filterCategory == null ? category.size() : filterCategory.size();
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public boolean hasStableIds() {
return false;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
private String getMapDescription(LocalIndexInfo child) {
if (child.getType() == LocalIndexType.TILES_DATA) {
return ctx.getString(R.string.online_map);
} else if (child.getFileName().endsWith(IndexConstants.BINARY_ROAD_MAP_INDEX_EXT)) {
return ctx.getString(R.string.download_roads_only_item);
} else if (child.isBackupedData() && child.getFileName().endsWith(IndexConstants.BINARY_WIKI_MAP_INDEX_EXT)) {
return ctx.getString(R.string.download_wikipedia_maps);
} else if (child.isBackupedData() && child.getFileName().endsWith(IndexConstants.BINARY_SRTM_MAP_INDEX_EXT)) {
return ctx.getString(R.string.download_srtm_maps);
}
return "";
}
private class LocalIndexInfoViewHolder {
private final TextView nameTextView;
private final ImageButton options;
private final ImageView icon;
private final TextView descriptionTextView;
private final CheckBox checkbox;
public LocalIndexInfoViewHolder(View view) {
nameTextView = ((TextView) view.findViewById(R.id.nameTextView));
options = (ImageButton) view.findViewById(R.id.options);
icon = (ImageView) view.findViewById(R.id.icon);
descriptionTextView = (TextView) view.findViewById(R.id.descriptionTextView);
checkbox = (CheckBox) view.findViewById(R.id.check_local_index);
}
public void bindLocalIndexInfo(final LocalIndexInfo child) {
options.setImageDrawable(ctx.getMyApplication().getIconsCache()
.getThemedIcon(R.drawable.ic_overflow_menu_white));
options.setContentDescription(ctx.getString(R.string.shared_string_more));
options.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
openPopUpMenu(v, child);
}
});
int colorId = filesToUpdate.containsKey(child.getFileName()) ? R.color.color_distance : R.color.color_ok;
if (child.isBackupedData()) {
colorId = R.color.color_unknown;
}
icon.setImageDrawable(getContentIcon(ctx, child.getType().getIconResource(), colorId));
nameTextView.setText(getNameToDisplay(child));
if (child.isNotSupported()) {
nameTextView.setTextColor(warningColor);
} else if (child.isCorrupted()) {
nameTextView.setTextColor(corruptedColor);
} else {
nameTextView.setTextColor(okColor);
}
if (child.isBackupedData()) {
nameTextView.setTypeface(Typeface.DEFAULT, Typeface.ITALIC);
} else {
nameTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL);
}
StringBuilder builder = new StringBuilder();
final String mapDescription = getMapDescription(child);
if (mapDescription.length() > 0) {
builder.append(mapDescription);
}
if (child.getSize() >= 0) {
if (builder.length() > 0) {
builder.append(" • ");
}
if (child.getSize() > 100) {
builder.append(DownloadActivity.formatMb.format(new Object[]{(float) child.getSize() / (1 << 10)}));
} else {
builder.append(child.getSize()).append(" KB");
}
}
if (!Algorithms.isEmpty(child.getDescription())) {
if (builder.length() > 0) {
builder.append(" • ");
}
builder.append(child.getDescription());
}
descriptionTextView.setText(builder.toString());
checkbox.setVisibility(selectionMode ? View.VISIBLE : View.GONE);
if (selectionMode) {
icon.setVisibility(View.GONE);
options.setVisibility(View.GONE);
checkbox.setChecked(selectedItems.contains(child));
checkbox.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkbox.isChecked()) {
selectedItems.add(child);
} else {
selectedItems.remove(child);
}
}
});
} else {
options.setVisibility(View.VISIBLE);
icon.setVisibility(View.VISIBLE);
}
}
private Drawable getContentIcon(DownloadActivity context, int resourceId, int colorId) {
return context.getMyApplication().getIconsCache().getIcon(resourceId, colorId);
}
}
}
private void openPopUpMenu(View v, final LocalIndexInfo info) {
IconsCache iconsCache = getMyApplication().getIconsCache();
final PopupMenu optionsMenu = new PopupMenu(getActivity(), v);
DirectionsDialogs.setupPopUpMenuIcon(optionsMenu);
final boolean restore = info.isBackupedData();
MenuItem item;
if ((info.getType() == LocalIndexType.MAP_DATA) || (info.getType() == LocalIndexType.DEACTIVATED)) {
item = optionsMenu.getMenu().add(restore ? R.string.local_index_mi_restore : R.string.local_index_mi_backup)
.setIcon(iconsCache.getThemedIcon(R.drawable.ic_type_archive));
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
performBasicOperation(restore ? R.string.local_index_mi_restore : R.string.local_index_mi_backup, info);
return true;
}
});
}
item = optionsMenu.getMenu().add(R.string.shared_string_rename)
.setIcon(iconsCache.getThemedIcon(R.drawable.ic_action_edit_dark));
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
performBasicOperation(R.string.shared_string_rename, info);
return true;
}
});
if (info.getType() == LocalIndexType.TILES_DATA && (info.getAttachedObject() instanceof ITileSource) &&
((ITileSource)info.getAttachedObject()).couldBeDownloadedFromInternet()) {
item = optionsMenu.getMenu().add(R.string.clear_tile_data)
.setIcon(iconsCache.getThemedIcon(R.drawable.ic_action_remove_dark));
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
performBasicOperation(R.string.clear_tile_data, info);
return true;
}
});
}
final IndexItem update = filesToUpdate.get(info.getFileName());
if (update != null) {
item = optionsMenu.getMenu().add(R.string.shared_string_download)
.setIcon(iconsCache.getThemedIcon(R.drawable.ic_action_import));
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
getDownloadActivity().startDownload(update);
return true;
}
});
}
item = optionsMenu.getMenu().add(R.string.shared_string_delete)
.setIcon(iconsCache.getThemedIcon(R.drawable.ic_action_delete_dark));
item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
performBasicOperation(R.string.shared_string_delete, info);
return true;
}
});
optionsMenu.show();
}
private DownloadActivity getDownloadActivity() {
return (DownloadActivity) getActivity();
}
public interface RenameCallback {
public void renamedTo(File file);
}
}